﻿using System;
using System.Collections.Generic;
using SpiceNet.Diagnostics;
using SpiceNet.Circuits;
using SpiceNet.Simulations;
using SpiceNet.Integration;
using SpiceNet.Parameters;

namespace SpiceNet.Models
{
    /// <summary>
    /// An instance of a resistor
    /// </summary>
    public class Resistor : Instance
    {
        /// <summary>
        /// Get parameters
        /// </summary>
        public override Dictionary<string, IP> ParamTable => _pTable;

        /// <summary>
        /// Private variables
        /// </summary>
        private double RESconduct;
        private int RESposNode, RESnegNode;
        private Noise.InstanceNoiseData RESnVar = new Noise.InstanceNoiseData();

        #region Parameters
        private enum _c
        {
            /* device parameters */
            RES_RESIST = 1,
            RES_WIDTH = 2,
            RES_LENGTH = 3,
            RES_CONDUCT = 4,
            RES_RESIST_SENS = 5,
            RES_CURRENT = 6,
            RES_POWER = 7,
            RES_TEMP = 8,
            /* device questions */
            RES_QUEST_SENS_REAL = 201,
            RES_QUEST_SENS_IMAG = 202,
            RES_QUEST_SENS_MAG = 203,
            RES_QUEST_SENS_PH = 204,
            RES_QUEST_SENS_CPLX = 205,
            RES_QUEST_SENS_DC = 206
        }
        private static Dictionary<string, IP> _pTable = new Dictionary<string, IP>
        {
            { "resistance", new IP(IF.IOPP, (int)_c.RES_RESIST, T.REAL, "Resistance") },
            { "temp", new IP(IF.IOPZU, (int)_c.RES_TEMP, T.REAL, "Instance operating temperature") },
            { "l", new IP(IF.IOPQU, (int)_c.RES_LENGTH, T.REAL, "Length") },
            { "w", new IP(IF.IOPZU, (int)_c.RES_WIDTH, T.REAL, "Width") },
            { "sens_resist", new IP(IF.IP, (int)_c.RES_RESIST_SENS, T.FLAG, "flag to request sensitivity WRT resistance") },
            { "i", new IP(IF.OP, (int)_c.RES_CURRENT, T.REAL, "Current") },
            { "p", new IP(IF.OP, (int)_c.RES_POWER, T.REAL, "Power") },
            { "sens_dc", new IP(IF.OPU, (int)_c.RES_QUEST_SENS_DC, T.REAL, "dc sensitivity ") },
            { "sens_real", new IP(IF.OPU, (int)_c.RES_QUEST_SENS_REAL, T.REAL, "dc sensitivity and real part of ac sensitivity") },
            { "sens_imag", new IP(IF.OPU, (int)_c.RES_QUEST_SENS_IMAG, T.REAL, "dc sensitivity and imag part of ac sensitivity") },
            { "sens_mag", new IP(IF.OPU, (int)_c.RES_QUEST_SENS_MAG, T.REAL, "ac sensitivity of magnitude") },
            { "sens_ph", new IP(IF.OPU, (int)_c.RES_QUEST_SENS_PH, T.REAL, "ac sensitivity of phase") },
            { "sens_cplx", new IP(IF.OPU, (int)_c.RES_QUEST_SENS_CPLX, T.COMPLEX, "ac sensitivity") }
        };

        /// <summary>
        /// Parameters
        /// </summary>
        public Parameter<double> REStemp{ get; } = new Parameter<double>();
        public Parameter<double> RESresist{ get; } = new Parameter<double>();
        public Parameter<double> RESwidth{ get; } = new Parameter<double>();
        public Parameter<double> RESlength{ get; } = new Parameter<double>();
        public int RESsenParmNo { get; set; }
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public Resistor(string name) : base(name, 2, typeof(ResistorModel)) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the resistor</param>
        /// <param name="posnode">The name of the positive node</param>
        /// <param name="negnode">The name of the negative node</param>
        /// <param name="resistance">The resistance (Ohm)</param>
        public Resistor(string name, string posnode, string negnode, double resistance) : this(name)
        {
            Connect(posnode, negnode);
            RESresist.Par(resistance);
        }

        /// <summary>
        /// Set parameters
        /// </summary>
        protected override void Param(int id, object value, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.RES_TEMP:
                    REStemp.Par((double)value + Circuit.CONSTCtoK);
                    break;
                case _c.RES_RESIST:
                    RESresist.Par((double)value);
                    break;
                case _c.RES_WIDTH:
                    RESwidth.Par((double)value);
                    break;
                case _c.RES_LENGTH:
                    RESlength.Par((double)value);
                    break;
                case _c.RES_RESIST_SENS:
                    RESsenParmNo = (int)value;
                    break;
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Get parameters
        /// </summary>
        protected override object Ask(int id, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.RES_TEMP:
                    return REStemp - Circuit.CONSTCtoK;
                case _c.RES_CONDUCT:
                    return RESconduct;
                case _c.RES_RESIST:
                    return RESresist.Value;
                case _c.RES_LENGTH:
                    return RESlength.Value;
                case _c.RES_WIDTH:
                    return RESwidth.Value;
                case _c.RES_CURRENT:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return (ckt.State.Solution[RESposNode] -
                                ckt.State.Solution[RESnegNode])
                                * RESconduct;
                    }
                case _c.RES_POWER:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return (ckt.State.Solution[RESposNode] -
                                ckt.State.Solution[RESnegNode]) *
                                RESconduct *
                                (ckt.State.Solution[RESposNode] -
                                ckt.State.Solution[RESnegNode]);
                    }
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Setup the resistor
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="model"></param>
        /// <param name="p"></param>
        public override void Setup(Circuit ckt, Model model, object p = null)
        {
            // Bind nodes
            var nodes = BindNodes(ckt);
            RESposNode = nodes[0].Number;
            RESnegNode = nodes[1].Number;
        }

        /// <summary>
        /// Do temperature-dependent stuff
        /// </summary>
        /// <param name="model"></param>
        /// <param name="ckt"></param>
        public override void Temperature(Circuit ckt, Model model, object p = null)
        {
            double factor;
            double difference;
            ResistorModel rmod = model as ResistorModel;

            /* Default Value Processing for Resistor Instance */
            if (!REStemp.Given) REStemp.Value = ckt.Config.Temperature;
            if (!RESwidth.Given) RESwidth.Value = rmod.RESdefWidth;
            if (!RESlength.Given) RESlength.Value = 0;
            if (!RESresist.Given)
            {
                if (rmod.RESsheetRes.Given && (rmod.RESsheetRes != 0) && (RESlength != 0))
                {
                    RESresist.Value = rmod.RESsheetRes * (RESlength - rmod.RESnarrow) / (RESwidth - rmod.RESnarrow);
                }
                else
                {
                    CircuitWarning.Warning(this, string.Format("{0}: resistance=0, set to 1000", Name ?? "NULL"));
                    RESresist.Value = 1000;
                }
            }

            difference = REStemp - rmod.REStnom;
            factor = 1.0 + (rmod.REStempCoeff1) * difference + (rmod.REStempCoeff2) * difference * difference;

            RESconduct = 1.0 / (RESresist * factor);
        }

        /// <summary>
        /// Load the resistor instance into the circuit matrix
        /// </summary>
        /// <param name="model"></param>
        /// <param name="ckt"></param>
        public override void Load(Circuit ckt, Model model, object p = null)
        {
            CircuitState state = ckt.State;
            state.Matrix[RESposNode, RESposNode] += RESconduct;
            state.Matrix[RESnegNode, RESnegNode] += RESconduct;
            state.Matrix[RESposNode, RESnegNode] -= RESconduct;
            state.Matrix[RESnegNode, RESposNode] -= RESconduct;
        }

        /// <summary>
        /// Load the resistor instance for AC analysis
        /// </summary>
        /// <param name="model"></param>
        /// <param name="ckt"></param>
        public override void AcLoad(Circuit ckt, Model model, object p = null)
        {
            CircuitStateComplex state = ckt.StateComplex;
            state.Matrix[RESposNode, RESposNode] += RESconduct;
            state.Matrix[RESnegNode, RESnegNode] += RESconduct;
            state.Matrix[RESposNode, RESnegNode] -= RESconduct;
            state.Matrix[RESnegNode, RESposNode] -= RESconduct;
        }

        /// <summary>
        /// Load noise data
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="mode"></param>
        /// <param name="operation"></param>
        /// <param name="data"></param>
        /// <param name="OnDens"></param>
        /// <param name="p"></param>
        public override void NoiseLoad(Circuit ckt,
            Noise.OutputMode mode, 
            Noise.Operation operation, 
            NoiseData data, ref double OnDens, Model model, object p = null)
        {
            double tempOutNoise;
            double tempInNoise;
            double noizDens = 0.0;
            double lnNdens = 0.0;
            NoiseJob curjob = ckt.CurrentJob as NoiseJob;
            var cstate = ckt.State;

            switch (operation)
            {
                case Noise.Operation.N_OPEN:
                    if (curjob.NStpsSm != 0)
                    {
                        switch (mode)
                        {
                            case Noise.OutputMode.N_DENS:
                                data.namelist.Add(string.Format("onoise_{0}", Name));
                                break;
                            case Noise.OutputMode.INT_NOIZ:
                                data.namelist.Add(string.Format("onoise_total_{0}", Name));
                                data.namelist.Add(string.Format("inoise_total_{0}", Name));
                                break;
                        }
                    }
                    break;

                case Noise.Operation.N_CALC:
                    switch (mode)
                    {
                        case Noise.OutputMode.N_DENS:
                            NevalSrc(ckt, ref noizDens, ref lnNdens, Noise.Sources.THERMNOISE,
                                RESposNode, RESnegNode, RESconduct);
                            OnDens += noizDens;

                            // If we haven't done any previous integration, we need to initialize our history variables
                            if (data.delFreq == 0.0)
                            {
                                RESnVar.LNLSTDENS = lnNdens;

                                // Clear out our integration variable if it's the first pass
                                if (data.freq == curjob.NstartFreq)
                                    RESnVar.OUTNOIZ = 0.0;
                            }
                            else // data.delFreq != 0.0 (we have to integrate)
                            {
                                tempOutNoise = NoiseIntegration.Integrate(noizDens, lnNdens, 
                                    RESnVar.LNLSTDENS, data);
                                tempInNoise = NoiseIntegration.Integrate(noizDens *
                                    data.GainSqInv, lnNdens + data.lnGainInv,
                                    RESnVar.LNLSTDENS + data.lnGainInv, data);
                                RESnVar.OUTNOIZ += tempOutNoise;
                                RESnVar.INNOIZ += tempInNoise;
                                data.outNoiz += tempOutNoise;
                                data.inNoise += tempInNoise;
                                RESnVar.LNLSTDENS = lnNdens;
                            }
                            if (data.prtSummary)
                                data.outpVector[data.outNumber++] = noizDens;
                            break;

                        case Noise.OutputMode.INT_NOIZ:
                            // Already calculated, just output
                            if (curjob.NStpsSm != 0)
                            {
                                data.outpVector[data.outNumber++] = RESnVar.OUTNOIZ;
                                data.outpVector[data.outNumber++] = RESnVar.INNOIZ;
                            }
                            break;
                    }
                    break;

                case Noise.Operation.N_CLOSE:
                    return;
            }
            return;
        }
    }
}
